/*
 * Copyright Nucleron R&D LLC 2016
 *
 * This file is licensed under the terms of YAPL,
 * see License.txt for details.
 */

#include <stdbool.h>

#include <plc_abi.h>

#include <plc_config.h>

#include <plc_rtc.h>
#include <plc_tick.h>
#include <plc_backup.h>
#include <plc_hw.h>
#include <plc_iom.h>
#include "plc_app.h"
#include "hwboard.h"
void SerialBegin(uint8_t serialno, int baud)
{
    Serials[serialno]->begin(baud);
}
uint16_t SerialRead(uint8_t serialno)
{
    return Serials[serialno]->read();
}
uint16_t SerialWrite(uint8_t serialno, uint8_t *buf, uint8_t len)
{
    return Serials[serialno]->write(buf, len);
}
int SerialAvailable(uint8_t serialno)
{
    return Serials[serialno]->available();
}
int create_thread(const char *name, void (*thread)(const void *), void *arg, int pori, int stack)
{
    osThreadId defaultTaskHandle;
    osThreadDef(name, thread, (osPriority)pori, 0, stack);
    defaultTaskHandle = osThreadCreate(osThread(name), arg);
}
#ifndef LOG_BUFFER_SIZE
#define LOG_BUFFER_SIZE 256 /*16Ko*/
#endif
#ifndef LOG_BUFFER_ATTRS
#define LOG_BUFFER_ATTRS
#endif

#define LOG_BUFFER_MASK (LOG_BUFFER_SIZE - 1)

char LogBuff[LOG_LEVELS][LOG_BUFFER_SIZE] LOG_BUFFER_ATTRS;
extern "C"
{
    void copy_to_log(uint8_t level, uint32_t buffpos, void *buf, uint32_t size)
    {
        if (buffpos + size < LOG_BUFFER_SIZE)
        {
            memcpy(&LogBuff[level][buffpos], buf, size);
        }
        else
        {
            uint32_t remaining = LOG_BUFFER_SIZE - buffpos;
            memcpy(&LogBuff[level][buffpos], buf, remaining);
            memcpy(LogBuff[level], (char *)buf + remaining, size - remaining);
        }
    }
    void copy_from_log(uint8_t level, uint32_t buffpos, void *buf, uint32_t size)
    {
        if (buffpos + size < LOG_BUFFER_SIZE)
        {
            memcpy(buf, &LogBuff[level][buffpos], size);
        }
        else
        {
            uint32_t remaining = LOG_BUFFER_SIZE - buffpos;
            memcpy(buf, &LogBuff[level][buffpos], remaining);
            memcpy((char *)buf + remaining, LogBuff[level], size - remaining);
        }
    }

    /* Log buffer structure

 |<-Tail1.msgsize->|<-sizeof(mTail)->|<--Tail2.msgsize-->|<-sizeof(mTail)->|...
 |  Message1 Body  |      Tail1      |   Message2 Body   |      Tail2      |

*/
    typedef struct
    {
        uint32_t msgidx;
        uint32_t msgsize;
        unsigned long tick;
        IEC_TIME time;
    } mTail;

    /* Log cursor : 64b
   |63 ... 32|31 ... 0|
   | Message | Buffer |
   | counter | Index  | */
    uint64_t LogCursor[LOG_LEVELS] LOG_BUFFER_ATTRS = {0x0, 0x0, 0x0, 0x0};

    void ResetLogCount(void)
    {
        uint8_t level;
        for (level = 0; level < LOG_LEVELS; level++)
        {
            LogCursor[level] = 0;
        }
    }
    uint32_t GetLogCount(uint8_t level)
    {
        return (uint64_t)LogCursor[level] >> 32;
    }

    /* Return message size and content */
    uint32_t GetLogMessage(uint8_t level, uint32_t msgidx, char *buf, uint32_t max_size, uint32_t *tick, uint32_t *tv_sec, uint32_t *tv_nsec)
    {
        uint64_t cursor = LogCursor[level];
        if (cursor)
        {
            /* seach cursor */
            uint32_t stailpos = (uint32_t)cursor;
            uint32_t smsgidx;
            mTail tail;
            tail.msgidx = cursor >> 32;
            tail.msgsize = 0;

            /* Message search loop */
            do
            {
                smsgidx = tail.msgidx;
                stailpos = (stailpos - sizeof(mTail) - tail.msgsize) & LOG_BUFFER_MASK;
                copy_from_log(level, stailpos, &tail, sizeof(mTail));
            } while ((tail.msgidx == smsgidx - 1) && (tail.msgidx > msgidx));

            if (tail.msgidx == msgidx)
            {
                uint32_t sbuffpos = (stailpos - tail.msgsize) & LOG_BUFFER_MASK;
                uint32_t totalsize = tail.msgsize;
                *tick = tail.tick;
                *tv_sec = tail.time.tv_sec;
                *tv_nsec = tail.time.tv_nsec;
                copy_from_log(level, sbuffpos, buf,
                              totalsize > max_size ? max_size : totalsize);
                return totalsize;
            }
        }
        return 0;
    }
    long AtomicCompareExchange(long *atomicvar, long compared, long exchange)
    {
        /* No need for real atomic op on LPC,
	 * no possible preemption between debug and PLC */
        long res = *atomicvar;
        if (res == compared)
        {
            *atomicvar = exchange;
        }
        return res;
    }

    uint64_t AtomicCompareExchange64(long long *atomicvar, long long compared, long long exchange)
    {
        /* No need for real atomic op on LPC,
	 * no possible preemption between debug and PLC */
        long long res = *atomicvar;
        if (res == compared)
        {
            *atomicvar = exchange;
        }
        return res;
    }
    /* Store one log message of give size */
    int LogMessage(uint8_t level, char *buf, uint32_t size)
    {
        if (size < LOG_BUFFER_SIZE - sizeof(mTail))
        {
            uint32_t buffpos;
            uint64_t new_cursor, old_cursor;

            mTail tail;
            tail.msgsize = size;
            tail.tick = millis();
            plc_rtc_time_get(&tail.time);

            /* We cannot increment both msg index and string pointer
           in a single atomic operation but we can detect having been interrupted.
           So we can try with atomic compare and swap in a loop until operation
           succeeds non interrupted */
            do
            {
                old_cursor = LogCursor[level];
                buffpos = (uint32_t)old_cursor;
                tail.msgidx = (old_cursor >> 32);
                new_cursor = ((uint64_t)(tail.msgidx + 1) << 32) | (uint64_t)((buffpos + size + sizeof(mTail)) & LOG_BUFFER_MASK);
            } while (AtomicCompareExchange64(
                         (long long *)&LogCursor[level],
                         (long long)old_cursor,
                         (long long)new_cursor) != (long long)old_cursor);

            copy_to_log(level, buffpos, buf, size);
            copy_to_log(level, (buffpos + size) & LOG_BUFFER_MASK, &tail, sizeof(mTail));

            return 1; /* Success */
        }
        else
        {
            char mstr[] = "Logging error : message too big";
            LogMessage(LOG_CRITICAL, mstr, sizeof(mstr));
        }
        return 0;
    }
}
const plc_rte_abi_t plc_glue_rte =
    {
        .get_time = plc_rtc_time_get,
        .set_timer = plc_tick_setup,

        .check_retain_buf = plc_backup_check,
        .invalidate_retain_buf = plc_backup_invalidate,
        .validate_retain_buf = plc_backup_validate,

        .retain = plc_backup_retain,
        .remind = plc_backup_remind,
        .retain_close = plc_backup_close,
        .set_dout = plc_set_dout,
        .set_aout = plc_set_aout,
        .get_din = plc_get_din,

        .get_ain = plc_get_ain,
        //Other unused rigth now
        .set_mem = 0,
        .get_mem = 0,
        .log_cnt_get = GetLogCount,
        .log_msg_get = GetLogMessage,
        .log_cnt_reset = ResetLogCount,
        .log_msg_post = LogMessage,

        .mb_abi = &mb_abi,
        .mb_request_abi = &mb_request_abi,
        .mb_master_abi = &mb_master_abi,
        .mb_slave_abi = &mb_slave_abi,
        .millils = millis,
        .SerialBegin = SerialBegin,
        .SerialRead = SerialRead,
        .SerialWrite = SerialWrite,
        .SerialAvailable = SerialAvailable,
        .create_thread = create_thread,
        .delay = vTaskDelay};

extern uint32_t _stack;
extern uint32_t _app_start;
extern uint32_t _app_end;

static bool plc_check_code(uint32_t *addr)
{
#ifdef RTE_APP
    if (addr < &_app_start)
    {
        return false;
    }
    if (addr > &_app_end)
    {
        return false;
    }
#endif
    return true;
}

#define PLC_CHECK_CODE(addr)                   \
    do                                         \
    {                                          \
        if (!plc_check_code((uint32_t *)addr)) \
        {                                      \
            plc_state = Empty;                 \
            return false;                      \
        }                                      \
    } while (0)
bool plc_app_is_valid(void)
{
    uint8_t i;
    bool is_correct = false;
#ifdef PLC
    return true;
#endif
//Check stack
///TODO: Add stack section to app!
#ifdef RTE_APP
    if (PLC_APP->sstart != &_stack)
    {
        plc_state = Empty;

        return false;
    }
#endif
    //Check that header was written
    //Check that app was written
    for (i = 0; i < 32; i++)
    {
        uint8_t tmp;
        tmp = PLC_APP->check_id[i];
        if (PLC_APP->id[i] != tmp)
        {
            plc_state = Empty;
            return false;
        }
        //Did we finish app write?
        if (tmp != 0xff)
        {
            plc_state = Empty;
            is_correct = true;
        }
    }
#ifndef PLC
    //Check bss section
    if (PLC_APP->bss_end >= &_stack)
    {
        plc_state = Empty;
        return false;
    }
    //Check data section
    if (PLC_APP->data_end > PLC_APP->bss_end)
    {
        plc_state = Empty;
        return false;
    }
    if (PLC_APP->data_start > PLC_APP->data_end)
    {
        plc_state = Empty;
        return false;
    }
    //Check pa section
    PLC_CHECK_CODE(PLC_APP->pa_start);
    PLC_CHECK_CODE(PLC_APP->pa_end);
    if (PLC_APP->pa_end < PLC_APP->pa_start)
    {
        plc_state = Empty;
        return false;
    }
    //Check ia section
    PLC_CHECK_CODE(PLC_APP->ia_start);
    PLC_CHECK_CODE(PLC_APP->ia_end);
    if (PLC_APP->ia_end < PLC_APP->ia_start)
    {
        plc_state = Empty;
        return false;
    }
    //Check fia section
    PLC_CHECK_CODE(PLC_APP->fia_start);
    PLC_CHECK_CODE(PLC_APP->fia_end);
    if (PLC_APP->fia_end < PLC_APP->fia_start)
    {
        plc_state = Empty;
        return false;
    }
    //HW ID
    if (strcmp(PLC_APP->hw_id, HWMODEL))
    {
        plc_state = Empty;
        return false;
    }
    //Check RTE version compatibility
    if (PLC_APP->rte_ver_major != PLC_RTE_VER_MAJOR)
    {
        plc_state = Empty;
        return false;
    }
    if (PLC_APP->rte_ver_minor > PLC_RTE_VER_MINOR)
    {
        plc_state = Empty;
        return false;
    }
    // if (PLC_APP->rte_ver_patch > PLC_RTE_VER_PATCH)
    // {
    //     plc_state = Empty;
    //     return false;
    // }
#endif

    //Check PLC application ABI
    PLC_CHECK_CODE(PLC_APP->start);
    PLC_CHECK_CODE(PLC_APP->stop);
    PLC_CHECK_CODE(PLC_APP->run);
    PLC_CHECK_CODE(PLC_APP->config_init);

    PLC_CHECK_CODE(PLC_APP->dbg_resume);
    PLC_CHECK_CODE(PLC_APP->dbg_suspend);

    PLC_CHECK_CODE(PLC_APP->dbg_data_get);
    PLC_CHECK_CODE(PLC_APP->dbg_data_free);

    PLC_CHECK_CODE(PLC_APP->dbg_vars_reset);
    PLC_CHECK_CODE(PLC_APP->dbg_var_register);

    //Check plc IO manager interface
    if (PLC_APP->l_tab < (plc_loc_tbl_t *)PLC_APP->data_start)
    {
        plc_state = Empty;
        return false;
    }
    if (PLC_APP->l_tab > (plc_loc_tbl_t *)PLC_APP->bss_end)
    {
        plc_state = Empty;
        return false;
    }

    if (PLC_APP->w_tab < PLC_APP->data_start)
    {
        plc_state = Empty;
        return false;
    }
    if (PLC_APP->w_tab > PLC_APP->bss_end)
    {
        plc_state = Empty;
        return false;
    }
    ///TODO: Add with CRC check
    return is_correct;
}

void plc_app_cstratup(void)
{
    volatile uint32_t *src, *dst, *end;
    app_fp_t *func, *func_end;
    //Init .data
    dst = PLC_APP->data_start;
    end = PLC_APP->data_end;
    src = PLC_APP->data_loadaddr;
    while (dst < end)
    {
        *dst++ = *src++;
    }
    //Init .bss
    end = PLC_APP->bss_end;
    while (dst < end)
    {
        *dst++ = 0;
    }
    // Constructors
    // .preinit_array
    func = PLC_APP->pa_start;
    func_end = PLC_APP->pa_end;
    while (func < func_end)
    {
        (*func)();
        func++;
    }
    // .init_array
    func = PLC_APP->ia_start;
    func_end = PLC_APP->ia_end;
    while (func < func_end)
    {
        (*func)();
        func++;
    }
}

int sys_read_outwords(u16 start_addr, u16 word_count, u16 *data_words)
{
    return board.modbus_cb_read(start_addr, word_count, data_words);
}
int sys_write_outwords(u16 start_addr, u16 word_count, u16 *data_words)
{
    return board.modbus_cb_write(start_addr, word_count, data_words);
}
int sys_read_outbits(u16 start_addr, u16 word_count, u8 *data_words)
{
    return board.modbus_cb_read_bits(start_addr, word_count, data_words);
}
int sys_write_outbits(u16 start_addr, u16 word_count, u8 *data_words)
{
    return board.modbus_cb_write_bits(start_addr, word_count, data_words);
}